// ArchiveCommandLine.cpp

#include "StdAfx.h"

#ifdef _WIN32
#ifndef UNDER_CE
#include <io.h>
#endif
#endif
#include <stdio.h>

#include "Common/FileSystem/ListFileUtils.h"
#include "Common/String/StringConvert.h"
#include "Common/String/StringToInt.h"

#include "Common/FileSystem/FileDir.h"
#include "Common/FileSystem/FileName.h"
#ifdef _WIN32
#include "Common/FileSystem/FileMapping.h"
#include "Common/System/Threading/Thread.h"
#endif

#include "ArchiveCommandLine.h"
#include "Common/FileSystem/EnumDirectory.h"
#include "Common/FileSystem/SortUtils.h"
#include "Common/Archive/Update.h"
#include "Common/Archive/UpdateAction.h"

#include <algorithm>

extern bool g_CaseSensitive;

#ifdef UNDER_CE

#define MY_IS_TERMINAL(x) false;

#else

#if _MSC_VER >= 1400
#define MY_isatty_fileno(x) _isatty(_fileno(x))
#else
#define MY_isatty_fileno(x) isatty(fileno(x))
#endif

#define MY_IS_TERMINAL(x) (MY_isatty_fileno(x) != 0);

#endif

using namespace Common;
using namespace System;
using namespace FileSystem;

int g_CodePage = -1;

namespace GUI
{
namespace App
{

namespace Key {
enum Enum
{
  kHelp1 = 0,
  kHelp2,
  kHelp3,
  kDisableHeaders,
  kDisablePercents,
  kArchiveType,
  kYes,
  #ifndef _NO_CRYPTO
  kPassword,
  #endif
  kProperty,
  kOutputDir,
  kWorkingDir,
  kInclude,
  kExclude,
  kArInclude,
  kArExclude,
  kNoArName,
  kUpdate,
  kVolume,
  kRecursed,
  kSfx,
  kStdIn,
  kStdOut,
  kOverwrite,
  kEmail,
  kShowDialog,
  kLargePages,
  kListfileCharSet,
  kConsoleCharSet,
  kTechMode,
  kShareForWrite,
  kCaseSensitive,
  kCalcCrc
};

}


static const wchar_t kRecursedIDChar = 'R';
static const wchar_t *kRecursedPostCharSet = L"0-";

  enum class RecursedPostCharIndex 
  {
	kWildCardRecursionOnly = 0,
	kNoRecursion = 1
  };

static const char kImmediateNameID = '!';
static const char kMapNameID = '#';
static const char kFileListID = '@';

static const char kSomeCludePostStringMinSize = 2; // at least <@|!><N>ame must be
static const char kSomeCludeAfterRecursedPostStringMinSize = 2; // at least <@|!><N>ame must be

static const wchar_t *kOverwritePostCharSet = L"asut";

Archive::Extract::OverwriteMode k_OverwriteModes[] =
{
  Archive::Extract::OverwriteMode::kWithoutPrompt,
  Archive::Extract::OverwriteMode::kSkipExisting,
  Archive::Extract::OverwriteMode::kAutoRename,
  Archive::Extract::OverwriteMode::kAutoRenameExisting
};

static const App::CSwitchForm kSwitchForms[] =
  {
	{ L"?",  App::SwitchType::kSimple, false },
	{ L"H",  App::SwitchType::kSimple, false },
	{ L"-HELP",  App::SwitchType::kSimple, false },
	{ L"BA", App::SwitchType::kSimple, false },
	{ L"BD", App::SwitchType::kSimple, false },
	{ L"T",  App::SwitchType::kUnLimitedPostString, false, 1 },
	{ L"Y",  App::SwitchType::kSimple, false },
	#ifndef _NO_CRYPTO
	{ L"P",  App::SwitchType::kUnLimitedPostString, false, 0 },
	#endif
	{ L"M",  App::SwitchType::kUnLimitedPostString, true, 1 },
	{ L"O",  App::SwitchType::kUnLimitedPostString, false, 1 },
	{ L"W",  App::SwitchType::kUnLimitedPostString, false, 0 },
	{ L"I",  App::SwitchType::kUnLimitedPostString, true, kSomeCludePostStringMinSize},
	{ L"X",  App::SwitchType::kUnLimitedPostString, true, kSomeCludePostStringMinSize},
	{ L"AI", App::SwitchType::kUnLimitedPostString, true, kSomeCludePostStringMinSize},
	{ L"AX", App::SwitchType::kUnLimitedPostString, true, kSomeCludePostStringMinSize},
	{ L"AN", App::SwitchType::kSimple, false },
	{ L"U",  App::SwitchType::kUnLimitedPostString, true, 1},
	{ L"V",  App::SwitchType::kUnLimitedPostString, true, 1},
	{ L"R",  App::SwitchType::kPostChar, false, 0, 0, kRecursedPostCharSet },
	{ L"SFX", App::SwitchType::kUnLimitedPostString, false, 0 },
	{ L"SI", App::SwitchType::kUnLimitedPostString, false, 0 },
	{ L"SO", App::SwitchType::kSimple, false, 0 },
	{ L"AO", App::SwitchType::kPostChar, false, 1, 1, kOverwritePostCharSet},
	{ L"SEML", App::SwitchType::kUnLimitedPostString, false, 0},
	{ L"AD",  App::SwitchType::kSimple, false },
	{ L"SLP", App::SwitchType::kUnLimitedPostString, false, 0},
	{ L"SCS", App::SwitchType::kUnLimitedPostString, false, 0},
	{ L"SCC", App::SwitchType::kUnLimitedPostString, false, 0},
	{ L"SLT", App::SwitchType::kSimple, false },
	{ L"SSW", App::SwitchType::kSimple, false },
	{ L"SSC", App::SwitchType::kPostChar, false, 0, 0, L"-" },
	{ L"SCRC", App::SwitchType::kSimple, false }
  };

static const App::CCommandForm g_CommandForms[] =
{
  { L"A", false },
  { L"U", false },
  { L"D", false },
  { L"T", false },
  { L"E", false },
  { L"X", false },
  { L"L", false },
  { L"B", false },
  { L"I", false }
};

static const int kNumCommandForms = sizeof(g_CommandForms) /  sizeof(g_CommandForms[0]);

static LPCTSTR kUniversalWildcard = _T("*");
static const size_t kMinNonSwitchWords = 1;
static const int kCommandIndex = 0;

// ---------------------------
// exception messages

static LPCTSTR kUserErrorMessage  = _T("Incorrect command line");
static LPCTSTR kCannotFindListFile = _T("Cannot find listfile");
static LPCTSTR kIncorrectListFile = _T("Incorrect item in listfile.\nCheck charset encoding and -scs switch.");
static LPCTSTR kIncorrectWildCardInListFile = _T("Incorrect wildcard in listfile");
static LPCTSTR kIncorrectWildCardInCommandLine  = _T("Incorrect wildcard in command line");
static LPCTSTR kTerminalOutError = _T("I won't write compressed data to a terminal");
static LPCTSTR kSameTerminalError = _T("I won't write data and program's messages to same terminal");
static LPCTSTR kEmptyFilePath = _T("Empty file path");

static void ThrowException(LPCTSTR errorMessage)
{
  throw CArchiveCommandLineException(errorMessage);
}

static void ThrowUserErrorException()
{
  ThrowException(kUserErrorMessage);
}

// ---------------------------

bool CArchiveCommand::IsFromExtractGroup() const
{
  switch(CommandType)
  {
	case CommandType::kTest:
	case CommandType::kExtract:
	case CommandType::kFullExtract:
	  return true;
	default:
	  return false;
  }
}

Archive::Extract::PathMode CArchiveCommand::GetPathMode() const
{
  switch(CommandType)
  {
	case CommandType::kTest:
	case CommandType::kFullExtract:
	  return Archive::Extract::PathMode::kFullPathnames;
	default:
	  return Archive::Extract::PathMode::kNoPathnames;
  }
}

bool CArchiveCommand::IsFromUpdateGroup() const
{
  return (CommandType == CommandType::kAdd ||
	CommandType == CommandType::kUpdate ||
	CommandType == CommandType::kDelete);
}

static RecursedType GetRecursedTypeFromIndex(int index)
{
  switch (index)
  {
	case RecursedPostCharIndex::kWildCardRecursionOnly:
	  return RecursedType::kWildCardOnlyRecursed;
	case RecursedPostCharIndex::kNoRecursion:
	  return RecursedType::kNonRecursed;
	default:
	  return RecursedType::kRecursed;
  }
}

static bool ParseArchiveCommand(const CString &commandString, CArchiveCommand &command)
{
  CString commandStringUpper = commandString;
  commandStringUpper.MakeUpper();
  CString postString;
  int commandIndex = ParseCommand(kNumCommandForms, g_CommandForms, commandStringUpper,
	  postString) ;
  if (commandIndex < 0)
	return false;
  command.CommandType = (CommandType)commandIndex;
  return true;
}

// ------------------------------------------------------------------
// filenames functions

static void AddNameToCensor(Wildcard::CCensor &wildcardCensor,
	const CString &name, bool include, RecursedType type)
{
  bool recursed = false;

  switch (type)
  {
	case RecursedType::kWildCardOnlyRecursed:
	  recursed = DoesNameContainWildCard(name);
	  break;
	case RecursedType::kRecursed:
	  recursed = true;
	  break;
  }
  wildcardCensor.AddItem(include, name, recursed);
}

static void AddToCensorFromListFile(Wildcard::CCensor &wildcardCensor,
	LPCWSTR fileName, bool include, RecursedType type, UINT codePage)
{
  CStringVector names;
  if (!FileSystem::Find::DoesFileExist(fileName))
	throw kCannotFindListFile;
  if (!ReadNamesFromListFile(fileName, names, codePage))
	throw kIncorrectListFile;
  for (size_t i = 0; i < names.size(); i++)
	AddNameToCensor(wildcardCensor, names[i], include, type);
}

static void AddToCensorFromNonSwitchesStrings(
	int startIndex,
	Wildcard::CCensor &wildcardCensor,
	const CStringVector &nonSwitchStrings, RecursedType type,
	bool thereAreSwitchIncludes, UINT codePage)
{
  if (nonSwitchStrings.size() == startIndex && (!thereAreSwitchIncludes))
	AddNameToCensor(wildcardCensor, kUniversalWildcard, true, type);
  for (size_t i = startIndex; i < nonSwitchStrings.size(); i++)
  {
	const CString &s = nonSwitchStrings[i];
	if (s.IsEmpty())
	  throw kEmptyFilePath;
	if (s[0] == kFileListID)
	  AddToCensorFromListFile(wildcardCensor, s.Mid(1), true, type, codePage);
	else
	  AddNameToCensor(wildcardCensor, s, true, type);
  }
}

#ifdef _WIN32
static void ParseMapWithPaths(Wildcard::CCensor &wildcardCensor,
	const CString &switchParam, bool include,
	RecursedType commonRecursedType)
{
  int splitPos = switchParam.Find(_T(':'));
  if (splitPos < 0)
	ThrowUserErrorException();
  CString mappingName = switchParam.Left(splitPos);
  
  CString switchParam2 = switchParam.Mid(splitPos + 1);
  splitPos = switchParam2.Find(L':');
  if (splitPos < 0)
	ThrowUserErrorException();
  
  CString mappingSize = switchParam2.Left(splitPos);
  CString eventName = switchParam2.Mid(splitPos + 1);
  
  UINT64 dataSize64 = _tcstoui64(mappingSize, NULL, 10);
  UINT32 dataSize = (UINT32)dataSize64;
  {
	CFileMapping fileMapping;
	if (fileMapping.Open(FILE_MAP_READ, String::GetSystemString(mappingName)) != 0)
	  ThrowException(_T("Can not open mapping"));
	LPVOID data = fileMapping.Map(FILE_MAP_READ, 0, dataSize);
	if (data == NULL)
	  ThrowException(_T("MapViewOfFile error"));
	try
	{
	  const wchar_t *curData = (const wchar_t *)data;
	  if (*curData != 0)
		ThrowException(_T("Incorrect mapping data"));
	  UINT32 numChars = dataSize / sizeof(wchar_t);
	  CString name;
	  for (UINT32 i = 1; i < numChars; i++)
	  {
		wchar_t c = curData[i];
		if (c == _T('\0'))
		{
		  AddNameToCensor(wildcardCensor, name, include, commonRecursedType);
		  name.Empty();
		}
		else
		  name += c;
	  }
	  if (!name.IsEmpty())
		ThrowException(_T("data error"));
	}
	catch(...)
	{
	  UnmapViewOfFile(data);
	  throw;
	}
	UnmapViewOfFile(data);
  }
  
  {
	  Threading::CEvent event;
	 
	if (event.Open(String::GetSystemString(eventName), EVENT_MODIFY_STATE, FALSE) == S_OK)
	  event.SetEvent();
  }
}
#endif

static void AddSwitchWildCardsToCensor(Wildcard::CCensor &wildcardCensor,
	const CStringVector &strings, bool include,
	RecursedType commonRecursedType, UINT codePage)
{
  for (size_t i = 0; i < strings.size(); i++)
  {
	const CString &name = strings[i];
		CString upperName(name);
		upperName = upperName.MakeUpper();
	RecursedType recursedType;
	int pos = 0;
	if (name.GetLength() < kSomeCludePostStringMinSize)
	  ThrowUserErrorException();
	//if (::MyCharUpper(name[pos]) == kRecursedIDChar) //AM: there was some fancy logic
		if (upperName[pos] == kRecursedIDChar)
	{
	  pos++;
	  int index = CString(kRecursedPostCharSet).Find(name[pos]);
	  recursedType = GetRecursedTypeFromIndex(index);
	  if (index >= 0)
		pos++;
	}
	else
	  recursedType = commonRecursedType;
	if (name.GetLength() < pos + kSomeCludeAfterRecursedPostStringMinSize)
	  ThrowUserErrorException();
	CString tail = name.Mid(pos + 1);
	if (name[pos] == kImmediateNameID)
	  AddNameToCensor(wildcardCensor, tail, include, recursedType);
	else if (name[pos] == kFileListID)
	  AddToCensorFromListFile(wildcardCensor, tail, include, recursedType, codePage);
	#ifdef _WIN32
	else if (name[pos] == kMapNameID)
	  ParseMapWithPaths(wildcardCensor, tail, include, recursedType);
	#endif
	else
	  ThrowUserErrorException();
  }
}

#ifdef _WIN32

// This code converts all short file names to long file names.

static void ConvertToLongName(const CString &prefix, CString &name)
{
  if (name.IsEmpty() || DoesNameContainWildCard(name))
	return;
  FileSystem::File::CFileInfo fi;
  if (fi.Find(prefix + name))
	name = fi.Name;
}

static void ConvertToLongNames(const CString &prefix, Wildcard::CCensorNode::ItemArrayType &items)
{
  for (size_t i = 0; i < items.size(); i++)
  {
	 const auto & item = items[i];
	if (item->Recursive || item->PathParts.size() != 1)
	  continue;
	ConvertToLongName(prefix, item->PathParts.front());
  }
}

static void ConvertToLongNames(const CString &prefix, std::shared_ptr<Wildcard::CCensorNode> &node)
{
  ConvertToLongNames(prefix, node->IncludeItems);
  ConvertToLongNames(prefix, node->ExcludeItems);
  auto & subNode = node->SubNodes;
  for (auto it = subNode.begin(); it !=  subNode.end(); ++it)
	ConvertToLongName(prefix, (*it)->Name);
  // mix folders with same name
  
  for (auto it1 = subNode.begin(); it1 != subNode.end(); ++it1)
  {
	auto & nextNode1 = *it1;
	for (auto it2 = it1 + 1; it2 != subNode.end(); ++it2)
	//for (size_t j = i + 1; j < node->SubNodes.size();)
	{
		const auto & nextNode2 = *it2;
	  if (nextNode1->Name.CompareNoCase(nextNode2->Name) == 0)
	  {
		  nextNode1->IncludeItems.insert(nextNode1->IncludeItems.end(),nextNode2->IncludeItems.cbegin(),nextNode2->IncludeItems.cend()) ;
		  nextNode1->ExcludeItems.insert(nextNode1->ExcludeItems.end(),nextNode2->ExcludeItems.cbegin(),nextNode2->ExcludeItems.cend()) ;
		  subNode.erase(it2);
	  }
	  else
		++it2;
	}
  }
  
 for (auto it = node->SubNodes.begin(); it !=  node->SubNodes.end(); ++it)
  {
	  //std::shared_ptr<Wildcard::CCensorNode> nextNode = node->SubNodes[i];
	  ConvertToLongNames(prefix + (*it)->Name + wchar_t(FileName::kDirDelimiter), *it);
  }
}

static void ConvertToLongNames(Wildcard::CCensor * censor)
{
	ATLASSERT(NULL != censor);
	  for (size_t i = 0; i < censor->Pairs.size(); i++)
	  {
		  Wildcard::CPair pair = censor->Pairs[i];
		ConvertToLongNames(pair.Prefix, pair.Head);
	  }
}

#endif

static Archive::Update::PairAction GetUpdatePairActionType(int i)
{
  switch(i)
  {
	case Archive::Update::PairAction::kIgnore: return Archive::Update::PairAction::kIgnore;
	case Archive::Update::PairAction::kCopy: return Archive::Update::PairAction::kCopy;
	case Archive::Update::PairAction::kCompress: return Archive::Update::PairAction::kCompress;
	case Archive::Update::PairAction::kCompressAsAnti: return Archive::Update::PairAction::kCompressAsAnti;
  }
  throw 98111603;
}

const CString kUpdatePairStateIDSet = L"PQRXYZW";
const int kUpdatePairStateNotSupportedActions[] = {2, 2, 1, -1, -1, -1, -1};

const CString kUpdatePairActionIDSet = L"0123"; //Ignore, Copy, Compress, Create Anti

const wchar_t *kUpdateIgnoreItselfPostStringID = L"-";
const wchar_t kUpdateNewArchivePostCharID = '!';


static bool ParseUpdateCommandString2(const CString &command,
	Archive::Update::CActionSet &actionSet, CString &postString)
{
	CString upperName(command);
	upperName = upperName.MakeUpper();
  for (int i = 0; i < upperName.GetLength();)
  {
	//wchar_t c = MyCharUpper(command[i]);
		TCHAR c = upperName[i];
	int statePos = kUpdatePairStateIDSet.Find(c);
	if (statePos < 0)
	{
	  postString = command.Mid(i);
	  return true;
	}
	i++;
	if (i >= command.GetLength())
	  return false;
	//int actionPos = kUpdatePairActionIDSet.Find(::MyCharUpper(command[i]));
		int actionPos = kUpdatePairActionIDSet.Find(upperName[i]);
	if (actionPos < 0)
	  return false;
	actionSet.StateActions[statePos] = GetUpdatePairActionType(actionPos);
	if (kUpdatePairStateNotSupportedActions[statePos] == actionPos)
	  return false;
	i++;
  }
  postString.Empty();
  return true;
}

static void ParseUpdateCommandString(Archive::CUpdateOptions &options,
	const CStringVector &updatePostStrings,
	const Archive::Update::CActionSet &defaultActionSet)
{
  for (size_t i = 0; i < updatePostStrings.size(); i++)
  {
	const CString &updateString = updatePostStrings[i];
	if (updateString.CompareNoCase(kUpdateIgnoreItselfPostStringID) == 0)
	{
	  if (options.UpdateArchiveItself)
	  {
		options.UpdateArchiveItself = false;
		options.Commands.pop_front();
	  }
	}
	else
	{
	  Archive::Update::CActionSet actionSet = defaultActionSet;

	  CString postString;
	  if (!ParseUpdateCommandString2(updateString, actionSet, postString))
		ThrowUserErrorException();
	  if (postString.IsEmpty())
	  {
		if (options.UpdateArchiveItself)
		  options.Commands[0]->ActionSet = actionSet;
	  }
	  else
	  {
			  CString upperPost(postString);
				upperPost = upperPost.MakeUpper();
		//if (MyCharUpper(postString[0]) != kUpdateNewArchivePostCharID)
				if (upperPost[0] != kUpdateNewArchivePostCharID)
		  ThrowUserErrorException();
				std::shared_ptr<Archive::CUpdateArchiveCommand> uc = std::make_shared<Archive::CUpdateArchiveCommand>();
		CString archivePath = postString.Mid(1);
		if (archivePath.IsEmpty())
		  ThrowUserErrorException();
		uc->UserArchivePath = archivePath;
		uc->ActionSet = actionSet;
		options.Commands.push_back(uc);
	  }
	}
  }
}

static const TCHAR kByteSymbol = _T('B');
static const TCHAR kKiloSymbol = _T('K');
static const TCHAR kMegaSymbol = _T('M');
static const TCHAR kGigaSymbol = _T('G');

static bool ParseComplexSize(const CString &src, UINT64 &result)
{
  CString s = src;
  s.MakeUpper();

  const TCHAR *start = s;
  const TCHAR *end;
  UINT64 number = String::ConvertStringToUInt64(start, &end);
  int numDigits = (int)(end - start);
  if (numDigits == 0 || s.GetLength() > numDigits + 1)
	return false;
  if (s.GetLength() == numDigits)
  {
	result = number;
	return true;
  }
  int numBits;
  switch (s[numDigits])
  {
	case kByteSymbol:
	  result = number;
	  return true;
	case kKiloSymbol:
	  numBits = 10;
	  break;
	case kMegaSymbol:
	  numBits = 20;
	  break;
	case kGigaSymbol:
	  numBits = 30;
	  break;
	default:
	  return false;
  }
  if (number >= ((UINT64)1 << (64 - numBits)))
	return false;
  result = number << numBits;
  return true;
}

static void SetAddCommandOptions(
	CommandType commandType,
	const App::CCmdLineParser &parser,
	Archive::CUpdateOptions &options)
{
  Archive::Update::CActionSet defaultActionSet;
  switch(commandType)
  {
	case CommandType::kAdd:
	  defaultActionSet = Archive::Update::kAddActionSet;
	  break;
	case CommandType::kDelete:
	  defaultActionSet = Archive::Update::kDeleteActionSet;
	  break;
	default:
	  defaultActionSet = Archive::Update::kUpdateActionSet;
  }
  
  options.UpdateArchiveItself = true;
  
  options.Commands.clear();
  std::shared_ptr<Archive::CUpdateArchiveCommand> updateMainCommand = std::make_shared<Archive::CUpdateArchiveCommand>();
  updateMainCommand->ActionSet = defaultActionSet;
  options.Commands.push_back(updateMainCommand);
  if (parser[Key::kUpdate].ThereIs)
	ParseUpdateCommandString(options, parser[Key::kUpdate].PostStrings,
		defaultActionSet);
  if (parser[Key::kWorkingDir].ThereIs)
  {
	const CString &postString = parser[Key::kWorkingDir].PostStrings[0];
	if (postString.IsEmpty())
	  FileSystem::GetTempPath(options.WorkingDir);
	else
	  options.WorkingDir = postString;
  }
  options.SfxMode = parser[Key::kSfx].ThereIs;
  if (options.SfxMode)
	options.SfxModule = parser[Key::kSfx].PostStrings[0];

  if (parser[Key::kVolume].ThereIs)
  {
	const CStringVector &sv = parser[Key::kVolume].PostStrings;
	options.VolumesSizes.reserve(options.VolumesSizes.size() + sv.size());
	for (size_t i = 0; i < sv.size(); i++)
	{
	  UINT64 size;
	  if (!ParseComplexSize(sv[i], size))
		ThrowException(_T("Incorrect volume size"));
	  options.VolumesSizes.push_back(size);
	}
  }
}

static void SetMethodOptions(const App::CCmdLineParser &parser, std::vector<Props::CProperty> &properties)
{
  if (parser[Key::kProperty].ThereIs)
  {
	for (size_t i = 0; i < parser[Key::kProperty].PostStrings.size(); i++)
	{
	  const CString &postString = parser[Key::kProperty].PostStrings[i];
	  properties.push_back(Props::CProperty(postString));
	}
  }
}

CArchiveCommandLineParser::CArchiveCommandLineParser():
  parser(sizeof(kSwitchForms) / sizeof(kSwitchForms[0])) {}

void CArchiveCommandLineParser::Parse1(const CStringVector &commandStrings,
	CArchiveCommandLineOptions &options)
{
  try
  {
	parser.ParseStrings(kSwitchForms, commandStrings);
  }
  catch(...)
  {
	ThrowUserErrorException();
  }

  options.IsInTerminal = MY_IS_TERMINAL(stdin);
  options.IsStdOutTerminal = MY_IS_TERMINAL(stdout);
  options.IsStdErrTerminal = MY_IS_TERMINAL(stderr);
  options.StdInMode = parser[Key::kStdIn].ThereIs;
  options.StdOutMode = parser[Key::kStdOut].ThereIs;
  options.EnableHeaders = !parser[Key::kDisableHeaders].ThereIs;
  options.HelpMode = parser[Key::kHelp1].ThereIs || parser[Key::kHelp2].ThereIs  || parser[Key::kHelp3].ThereIs;

  #ifdef _WIN32
  options.LargePages = false;
  if (parser[Key::kLargePages].ThereIs)
  {
	const CString &postString = parser[Key::kLargePages].PostStrings.front();
	if (postString.IsEmpty())
	  options.LargePages = true;
  }
  #endif
}

struct CCodePagePair
{
  const wchar_t *Name;
  UINT CodePage;
};

static CCodePagePair g_CodePagePairs[] =
{
  { L"UTF-8", CP_UTF8 },
  { L"WIN", CP_ACP },
  { L"DOS", CP_OEMCP }
};

static int FindCharset(const App::CCmdLineParser &parser, int keyIndex, int defaultVal)
{
  if (!parser[keyIndex].ThereIs)
	return defaultVal;

  CString name = parser[keyIndex].PostStrings.back();
  name.MakeUpper();
  int i;
  for (i = 0; i < sizeof(g_CodePagePairs) / sizeof(g_CodePagePairs[0]); i++)
  {
	const CCodePagePair &pair = g_CodePagePairs[i];
	if (name.Compare(pair.Name) == 0)
	  return pair.CodePage;
  }
  if (i == sizeof(g_CodePagePairs) / sizeof(g_CodePagePairs[0]))
	ThrowUserErrorException();
  return -1;
}

static bool ConvertStringToUInt32(const wchar_t *s, UINT32 &v)
{
  const wchar_t *end;
  UINT64 number = String::ConvertStringToUInt64(s, &end);
  if (*end != 0)
	return false;
  if (number > (UINT32)0xFFFFFFFF)
	return false;
  v = (UINT32)number;
  return true;
}

void EnumerateDirItemsAndSort(Wildcard::CCensor &wildcardCensor,
	std::vector<CString> &sortedPaths,
	std::vector<CString> &sortedFullPaths)
{
  {
	sortedPaths.clear();
	sortedFullPaths.clear();
	CDirItems dirItems;
	{
	  CStringVector errorPaths;
	  std::vector<DWORD> errorCodes;
	  HRESULT res = EnumerateItems(wildcardCensor, dirItems, NULL, errorPaths, errorCodes);
	  if (res != S_OK || errorPaths.size() > 0)
		throw "cannot find archive";
	}
	
	sortedPaths.reserve(dirItems.Items.size());
	for (CDirItems::PtrArrayItems::const_iterator it = dirItems.Items.begin(); 
		it != dirItems.Items.end(); ++it)
	{
	  
	  if (!(*it)->IsDir())
		  sortedPaths.push_back(dirItems.GetPhyPath(*it));
	}
  }

  if (sortedPaths.size() == 0)
	throw "there is no such archive";
  
  FileSystem::SortFileNames(sortedPaths);
  
  size_t i;
  sortedFullPaths.reserve(sortedPaths.size());
  for (i = 0; i < sortedPaths.size(); i++)
  {
	CString fullPath;
	FileSystem::GetFullPathName(sortedPaths[i], fullPath);
	sortedFullPaths.push_back(fullPath);
  }
}

void CArchiveCommandLineParser::Parse2(CArchiveCommandLineOptions &options)
{
  const CStringVector &nonSwitchStrings = parser.NonSwitchStrings;
  size_t numNonSwitchStrings = nonSwitchStrings.size();
  if (numNonSwitchStrings < kMinNonSwitchWords)
	ThrowUserErrorException();

  if (!ParseArchiveCommand(nonSwitchStrings[kCommandIndex], options.Command))
	ThrowUserErrorException();

  options.TechMode = parser[Key::kTechMode].ThereIs;
  options.CalcCrc = parser[Key::kCalcCrc].ThereIs;

  if (parser[Key::kCaseSensitive].ThereIs)
	g_CaseSensitive = (parser[Key::kCaseSensitive].PostCharIndex < 0);

  RecursedType recursedType;
  if (parser[Key::kRecursed].ThereIs)
	recursedType = GetRecursedTypeFromIndex(parser[Key::kRecursed].PostCharIndex);
  else
	recursedType = RecursedType::kNonRecursed;

  g_CodePage = FindCharset(parser, Key::kConsoleCharSet, -1);
  UINT codePage = FindCharset(parser, Key::kListfileCharSet, CP_UTF8);

  bool thereAreSwitchIncludes = false;
  if (parser[Key::kInclude].ThereIs)
  {
	thereAreSwitchIncludes = true;
	AddSwitchWildCardsToCensor(options.WildcardCensor,
		parser[Key::kInclude].PostStrings, true, recursedType, codePage);
  }
  if (parser[Key::kExclude].ThereIs)
	AddSwitchWildCardsToCensor(options.WildcardCensor,
		parser[Key::kExclude].PostStrings, false, recursedType, codePage);
 
  size_t curCommandIndex = kCommandIndex + 1;
  bool thereIsArchiveName = !parser[Key::kNoArName].ThereIs &&
	  options.Command.CommandType != CommandType::kBenchmark &&
	  options.Command.CommandType != CommandType::kInfo;

  bool isExtractGroupCommand = options.Command.IsFromExtractGroup();
  bool isExtractOrList = isExtractGroupCommand || options.Command.CommandType == CommandType::kList;

  if (isExtractOrList && options.StdInMode)
	thereIsArchiveName = false;

  if (thereIsArchiveName)
  {
	if (curCommandIndex >= numNonSwitchStrings)
	  ThrowUserErrorException();
	options.ArchiveName = nonSwitchStrings[curCommandIndex++];
	if (options.ArchiveName.IsEmpty())
	  ThrowUserErrorException();
  }

  AddToCensorFromNonSwitchesStrings(
	  curCommandIndex, options.WildcardCensor,
	  nonSwitchStrings, recursedType, thereAreSwitchIncludes, codePage);

  options.YesToAll = parser[Key::kYes].ThereIs;


  #ifndef _NO_CRYPTO
  options.PasswordEnabled = parser[Key::kPassword].ThereIs;
  if (options.PasswordEnabled)
	options.Password = parser[Key::kPassword].PostStrings[0];
  #endif

  options.ShowDialog = parser[Key::kShowDialog].ThereIs;

  if (parser[Key::kArchiveType].ThereIs)
	options.ArcType = parser[Key::kArchiveType].PostStrings[0];

  if (isExtractOrList)
  {
	if (!options.WildcardCensor.AllAreRelative())
	  ThrowException(_T("Cannot use absolute pathnames for this command"));

	Wildcard::CCensor archiveWildcardCensor;

	if (parser[Key::kArInclude].ThereIs)
	  AddSwitchWildCardsToCensor(archiveWildcardCensor,
		  parser[Key::kArInclude].PostStrings, true, RecursedType::kNonRecursed, codePage);
	if (parser[Key::kArExclude].ThereIs)
	  AddSwitchWildCardsToCensor(archiveWildcardCensor,
		  parser[Key::kArExclude].PostStrings, false, RecursedType::kNonRecursed, codePage);

	if (thereIsArchiveName)
	  AddNameToCensor(archiveWildcardCensor, options.ArchiveName, true, RecursedType::kNonRecursed);

	#ifdef _WIN32
	ConvertToLongNames(&archiveWildcardCensor);
	#endif

	archiveWildcardCensor.ExtendExclude();

	if (options.StdInMode)
	{
	  CString arcName = parser[Key::kStdIn].PostStrings.front();
	  options.ArchivePathsSorted.push_back(arcName);
	  options.ArchivePathsFullSorted.push_back(arcName);
	}
	else
	{
	  EnumerateDirItemsAndSort(archiveWildcardCensor,
		options.ArchivePathsSorted,
		options.ArchivePathsFullSorted);
	}
	
	if (isExtractGroupCommand)
	{
	  SetMethodOptions(parser, options.ExtractProperties);
	  if (options.StdOutMode && options.IsStdOutTerminal && options.IsStdErrTerminal)
		throw kSameTerminalError;
	  if (parser[Key::kOutputDir].ThereIs)
	  {
		options.OutputDir = parser[Key::kOutputDir].PostStrings[0];
		FileSystem::FileName::NormalizeDirPathPrefix(options.OutputDir);
	  }

	  options.OverwriteMode = Archive::Extract::OverwriteMode::kAskBefore;
	  if (parser[Key::kOverwrite].ThereIs)
		options.OverwriteMode = k_OverwriteModes[parser[Key::kOverwrite].PostCharIndex];
	  else if (options.YesToAll)
		options.OverwriteMode = Archive::Extract::OverwriteMode::kWithoutPrompt;
	}
  }
  else if (options.Command.IsFromUpdateGroup())
  {
	  Archive::CUpdateOptions &updateOptions = options.UpdateOptions;

	SetAddCommandOptions(options.Command.CommandType, parser, updateOptions);
	
	SetMethodOptions(parser, updateOptions.MethodMode.Properties);

	if (parser[Key::kShareForWrite].ThereIs)
	  updateOptions.OpenShareForWrite = true;

	options.EnablePercents = !parser[Key::kDisablePercents].ThereIs;

	if (options.EnablePercents)
	{
	  if ((options.StdOutMode && !options.IsStdErrTerminal) ||
		 (!options.StdOutMode && !options.IsStdOutTerminal))
		options.EnablePercents = false;
	}

	updateOptions.EMailMode = parser[Key::kEmail].ThereIs;
	if (updateOptions.EMailMode)
	{
	  updateOptions.EMailAddress = parser[Key::kEmail].PostStrings.front();
	  if (updateOptions.EMailAddress.GetLength() > 0)
		if (updateOptions.EMailAddress[0] == _T('.'))
		{
		  updateOptions.EMailRemoveAfter = true;
		  updateOptions.EMailAddress.Delete(0);
		}
	}

	updateOptions.StdOutMode = options.StdOutMode;
	updateOptions.StdInMode = options.StdInMode;

	if (updateOptions.StdOutMode && updateOptions.EMailMode)
	  throw "stdout mode and email mode cannot be combined";
	if (updateOptions.StdOutMode && options.IsStdOutTerminal)
	  throw kTerminalOutError;
	if (updateOptions.StdInMode)
	  updateOptions.StdInFileName = parser[Key::kStdIn].PostStrings.front();

	#ifdef _WIN32
	ConvertToLongNames(&options.WildcardCensor);
	#endif
  }
  else if (options.Command.CommandType == CommandType::kBenchmark)
  {
	options.NumThreads = (UINT32)-1;
	options.DictionarySize = (UINT32)-1;
	options.NumIterations = 1;
	if (curCommandIndex < numNonSwitchStrings)
	{
	  if (!ConvertStringToUInt32(nonSwitchStrings[curCommandIndex++], options.NumIterations))
		ThrowUserErrorException();
	}
	for (size_t i = 0; i < parser[Key::kProperty].PostStrings.size(); i++)
	{
	  CString postString = parser[Key::kProperty].PostStrings[i];
	  postString.MakeUpper();
	  if (postString.GetLength() < 2)
		ThrowUserErrorException();
	  if (postString[0] == _T('D'))
	  {
		int pos = 1;
		if (postString[pos] == _T('='))
		  pos++;
		UINT32 logSize;
		if (!ConvertStringToUInt32((const wchar_t *)postString + pos, logSize))
		  ThrowUserErrorException();
		if (logSize > 31)
		  ThrowUserErrorException();
		options.DictionarySize = 1 << logSize;
	  }
	  else if (postString[0] == _T('M') && postString[1] == _T('T') )
	  {
		int pos = 2;
		if (postString[pos] == _T('='))
		  pos++;
		if (postString[pos] != 0)
		  if (!ConvertStringToUInt32((const wchar_t *)postString + pos, options.NumThreads))
			ThrowUserErrorException();
	  }
	  else if (postString[0] == _T('M') && postString[1] == _T('=') )
	  {
		int pos = 2;
		if (postString[pos] != 0)
		  options.Method = postString.Mid(2);
	  }
	  else
		ThrowUserErrorException();
	}
  }
  else if (options.Command.CommandType == CommandType::kInfo)
  {
  }
  else
	ThrowUserErrorException();
  options.WildcardCensor.ExtendExclude();
}

}

}